<!DOCTYPE html>
<html>
  <head>
    <!-- [[! Document Settings ]] -->
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />

    <!-- [[! Page Meta ]] -->
    <title>[译] Android 架构：Part 2 —— 介绍 Clean Architecture</title>
    <meta name="description" content="在编码 - " />

    <meta name="HandheldFriendly" content="True" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="shortcut icon" href="/assets/images/favicon.ico" >

    <!-- [[! Styles'n'Scripts ]] -->
    <link rel="stylesheet" type="text/css" href="/assets/css/screen.css" />
    <link rel="stylesheet" type="text/css"
      href="//fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" />

    <link rel="stylesheet" type="text/css" href="/assets/css/prism.css" />

    <!-- [[! Ghost outputs important style and meta data with this tag ]] -->
    <link rel="canonical" href="/" />
<meta name="referrer" content="origin" />
<link rel="next" href="/page2/" />

<meta property="og:site_name" content="在编码" />
<meta property="og:type" content="website" />
<meta property="og:title" content="在编码" />
<meta property="og:description" content="" />
<meta property="og:url" content="/" />
<meta property="og:image" content="/assets/images/cover1.jpg" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="在编码" />
<meta name="twitter:description" content="" />
<meta name="twitter:url" content="/" />
<meta name="twitter:image:src" content="/assets/images/cover1.jpg" />

<script type="application/ld+json">
  {
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "Finding The Way Home",
  "url": "/",
  "image": "/assets/images/cover1.jpg",
  "description": ""
  }
</script>

<meta name="generator" content="Jekyll 3.0.0" />
<link rel="alternate" type="application/rss+xml" title="在编码" href="/feed.xml" />


  </head>
  <body class="home-template nav-closed">

    <div class="nav">
  <h3 class="nav-title">Home</h3>
  <a href="#" class="nav-close">
    <span class="hidden">Close</span>
  </a>
  <ul>
    <li class="nav-home " role="presentation"><a href="/">Home</a></li>
    <!-- <li class="nav-about " role="presentation"><a href="/about">About</a></li>
    <li class="nav-author " role="presentation"><a href="/author/casper">Author</a></li> -->
    <li class="nav-ios " role="presentation"><a href="/tag/iOS">iOS</a></li>
    <li class="nav-mvc " role="presentation"><a href="/tag/Android">Android</a></li>
    <li class="nav-server " role="presentation"><a href="/tag/Server">Server</a></li>
  </ul>
  <a class="subscribe-button icon-feed" href="/feed.xml">Subscribe</a>
</div>
<span class="nav-cover"></span>


    <div class="site-wrapper">

      <!-- [[! Everything else gets inserted here ]] -->
      <!-- < default -->

<!-- The comment above "< default" means - insert everything in this file into -->
<!-- the [body] of the default.hbs template, which contains our header/footer. -->

<!-- Everything inside the #post tags pulls data from the post -->
<!-- #post -->

<header class="main-header post-head no-cover">
  <nav class="main-nav overlay clearfix">
      <a class="back-button icon-arrow-left" href='/'>Home</a>
    <a class="menu-button icon-menu" href="#"><span class="word">Menu</span></a>
  </nav>
</header>

<main class="content" role="main">

  <article class="post">

    <header class="post-header">
      <h1 class="post-title">[译] Android 架构：Part 2 —— 介绍 Clean Architecture</h1>
      <section class="post-meta">
        <a href='/'>Listen</a>
        <time class="post-date" datetime="2017-07-27">27 Jul 2017</time>
        <!-- [[tags prefix=" on "]] -->
        
        on
        
        
        <a href='/tag/Android'>Android</a>,
        
        
        
        <a href='/tag/Clean Architecture'>Clean Architecture</a>
        
        
        
      </section>
    </header>

    <section class="post-content">

      <p>在本系列的第一部分，我们介绍了我们在寻找可行架构的道路上所犯过的错误。在这部分，我们将介绍传说中的 Clean Architecture。</p>

<p>当你在谷歌搜索 &quot;clean architecture&quot; 时，你看到的第一张图片是：</p>

<p><img src="../images/android/Graph-1.png" alt=""></p>

<p>它也被称为洋葱架构，因为图看起来象个洋葱（你会意识到你需要写样板代码写到哭）；或者是端口和适配器，因为你可以看到右图的一些端口。六角架构是另一个相似的架构。</p>

<p>Clean Architecture 是前面提到的 Uncle Bob 的心血结晶，他是 《代码整洁之道》的作者。这种方法的要点是，业务逻辑（也称为 domain），是宇宙的核心。</p>

<h2>掌控你的领域（domain）</h2>

<p>当你打开项目时，你应该已经知道这个 app 是做什么的，与技术无关。其它一切都是实现细节。譬如，持久化就是一个细节。定义接口，创建一个快速的粗糙的内存内（in-memory）实现，不要想太多，直到完成业务。然后你可以决定怎样真正地持久化数据。数据库，网络，两者结合，文件系统 —— 或者仍然保留在内存中，或者结果你根本不需要持久化。总之一句话：内层包含业务逻辑，外层包含实现细节。</p>

<p>话说回来， Clean Architectue 有一些特性使这成为可能：</p>

<ol>
<li>依赖规则</li>
<li>抽象</li>
<li>层与层之间的通信</li>
</ol>

<h3>I.依赖规则</h3>

<p>依赖规则可以用下图解释：</p>

<p><img src="../images/android/Graph-2.png" alt=""></p>

<p>外层应该依赖内层。那三个在红色框框内的箭头表示依赖。与其使用“依赖”，也许使用“看见”、“知道”、“了解”这类术语更好。在这些术语中，外层看见，知道，了解内层，但内层看不见，也不知道，更不了解外层。正如我们先前所说，内存包含业务逻辑，外层包含实现细节。遵循依赖规则，业务逻辑既看不到，也不知道，更不了解实现细节。这正是我们努力想要做到的。</p>

<p>如何实现依赖规则取决于你。你可以把它们放到不同的包，但小心“内层的”包不要使用“外层的”包。然而，如果有人不知道依赖规则，没有什么可以阻止他破坏规则。一个更好的方法是把层分离到不同的 Android 模块（modules，即子项目），并在构建文件（build.grale）中调整依赖，这样内层就无法依赖外层。</p>

<p>还有值得一提的是，虽然没人可以阻止你跨层依赖，譬如蓝色的层的组件使用红色的层的组件，但我强烈建议你只访问相邻的层的组件。</p>

<h3>II.抽象</h3>

<p>抽象原则之前已有所暗示。也就是说，当你朝图中间移动时，东西变得更抽象。 这是有道理的：正如我们所说内层包含业务逻辑，而外层包含实现细节。</p>

<p><img src="../images/android/Graph-3.png" alt=""></p>

<p>甚至可以在多个层之间划分相同的逻辑组件，如图所示。 内层定义更抽象的部分，外层定义更具体的部分。</p>

<p>举个例子说清楚些。我们可以定义一个 “Notifications” 的抽象接口，并将其放到内层，这样你的业务逻辑需要时可以使用它来向用户显示通知。另一方面，我们可以这样来实现该接口，即使用 Android NotificationManager 显示通知来实现，并把该实现放到外层。</p>

<p>以这种方式，业务逻辑可以使用这样的功能 —— 通知（在我们的例子中）—— 但它不了解实现细节：实际的通知是如何实现的。此外，业务逻辑甚至不知道实现细节的存在。来看下面这张图片：</p>

<p><img src="../images/android/Graph-4.png" alt=""></p>

<p>当将抽象规则和依赖规则组合在一起时，结果是使用通知的抽象业务逻辑既不会看到，也不会知道，更不会了解使用 Android NotificationManager 的具体实现。这很好，因为我们可以在业务逻辑毫不知情的情况下切换具体实现。</p>

<p>让我们把这种规则组合和标准的三层架构简单对比下，看看它们各自的抽象和依赖是怎样的以及如何工作的。</p>

<p><img src="../images/android/Graph-5.png" alt=""></p>

<p>在图中，你可以看到，标准三层架构的所有依赖最终都传到数据库。也就是说，抽象和依赖（方向）并不一致。在逻辑上，业务层应该是 app 的中心，但它却不是，因为依赖朝向数据库。</p>

<p>业务层不应该知道数据库，应该反过来。在 Clean Architecture 中，依赖朝向业务层（内层），并且抽象也上升到业务层，因此它们很好地匹配。</p>

<p>这是重要的，因为抽象是理论，依赖是实践。抽象是 app 的逻辑布局，依赖关系是（组件）如何实际组合在一起。在 Clean Architecture 中，这两者是匹配（译者注：指方向一致）的。而在标准三层架构中则不然，如果你不小心，很容易导致各种逻辑上的不一致和混乱。</p>

<h3>III.层与层之间的通信</h3>

<p>现在我们将 app 分模块，将所有内容分开，将业务逻辑放在我们 app 的中心，并在外层实现细节，一切看起来都很棒。 但是你可能很快遇到一个有趣的问题。</p>

<p>如果你的 UI 是一个实现细节，网络是一个实现细节，业务逻辑在中间，那么我们如何从互联网获取数据，经过业务逻辑，然后发送到界面？</p>

<p>业务逻辑在中间，应该协调网络和界面，但它甚至不知道两者的存在。这是一个关于通信和数据流的问题。</p>

<p>我们希望数据能够从外层流向内层，反之亦然，但依赖规则不允许。 让我们举个最简单的例子。</p>

<p>我们只有两层，绿色和红色的。绿色的是外层，它知道红色的，红色的是内层，它只知道自己。我们希望数据从绿色流向红色，然后折回绿色。该解决方案先前已经暗示过了，看下图：</p>

<p><img src="../images/android/Graph-6.png" alt=""></p>

<p>图的右边部分显示了数据流。数据源于 Controller，经过 UseCase（或者替换成你选择的组件）的输入端口，然后通过 UseCase 本身，最后通过 UseCase 输出端口发送到 Presenter。</p>

<p>图的主要部分（左边）的箭头表示组合和继承 —— 组合用实心箭头表示，继承用空心箭头表示。组合也被称作 has-a 关系，继承被称作 is-a 关系。圆圈中的 “I” 和 “O” 表示输入和输出端口。可以看到，定义在绿色层中的 Controller，拥有一个（has-a）定义在红色层中的输入端口。UseCase（齿轮，业务逻辑，现在不重要）是一个（is-a）（或实现）输入端口，并且拥有一个（has-a）输出端口。最后，定义在绿色层中的 Presenter 实际上是一个（is-a）定义在红色层的输出端口。</p>

<p>现在，我们可以将其与数据流匹配。Controller 拥有一个输入端口 —— 拥有一个指向它的引用。它调用输入端口的一个方法，这样数据就从 Controller 流到输入端口。但输入端口是一个接口，而它的实际实现是 UseCase。也就是说，它调用 UseCase 的一个方法，这样数据就流向了 UseCase。UseCase 执行某些操作，并希望将数据发送回来。它拥有输出端口的一个引用 —— 输出端口定义在同一层 —— 因此它可以调用上面的方法。因此，数据流向输出端口。最后 Presenter 是，或者实现了输出端口，这是魔法的一部分。因为它实现了输出端口，数据实际上流到它那了。</p>

<p>巧妙的是，UseCase 只知道它的输出端口，世界在此停止（意指数据流到此结束）。Presenter 实现了它（输出端口），实际上它可以被任何对象实现，因为 UseCase 不知道或不关心这些，它只清楚其层内的一亩三分地。可以看到，通过结合组合和继承，我们可以使数据流向两个方向，尽管内层并不知道它们在和外部世界通信。瞄一眼下图：</p>

<p><img src="../images/android/Graph-7.png" alt=""></p>

<p>可以看到，和依赖箭头一样，has-a 和 is-a 箭头也指向中间。这是符合逻辑的。根据依赖规则，这是唯一可行的方法。外层可以看到内层，但不能反过来。唯一复杂的部分是，is-a 关系尽管指向了中间，却反转了数据流。</p>

<p>请注意，定义输入和输出端口是内层自己的职责，因此外层可以使用它们与其建立通信。我说过，这个解决方案先前已经暗示过，而且已经有了。那个讲解抽象的通知例子，也是这种通信的一个例子。我们在内层定义了一个通知接口，业务逻辑可以用来向用户显示通知，但是我们在外层也定义一个实现。在这种情况下，通知接口是业务逻辑的输出端口，用来和外部世界（在本例中，就是和具体的实现）通信。你不需要把你的类命名为 FooOutputPort 或者 BarInputPort，我们命名端口只是为了解释理论。</p>

<h2>总结</h2>

<p>那么，它是过度复杂，过度费解的过度工程吗？好吧，当你习惯了，它就简单。并且这是必要的。它允许我们使得好的抽象／依赖实际匹配真实世界的通信和工作。也许这一切都提醒你不过是空中楼阁：美丽，理论上优雅，但过于复杂，我们仍然不知它是否有效，但在我们的案例中，它确实有效。</p>

<p>这就是本系列的第二部分。<a href="android-architecture-part-3-applying-clean-architecture-android.html">最后，第三部分</a>，毕竟我们已经了解了理论和架构，将讲解所有你需要了解的那些图上的标签。换句话说，分离的组件。我们将向你展示一个真实的应用于 Android 的 Clean Architecture。</p>

<p><a href="http://five.agency/android-architecture-part-2-clean-architecture/">原文</a></p>


    </section>

    <footer class="post-footer">

      <!-- Everything inside the #author tags pulls data from the author -->
      <!-- #author-->

      
      <figure class="author-image">
        <a class="img" href='/' style="background-image: url(/assets/images/me.jpg)"><span class="hidden">'s Picture</span></a>
      </figure>
      

      <section class="author">
        <h4><a href='/'>Listen</a></h4>

        
        <p> 移动开发工程师，熟悉 Android、iOS、ReactNative</p>
        
        <div class="author-meta">
          <span class="author-location icon-location"> 深圳</span>
          <span class="author-link icon-link"><a href="https://github.com/listenzz"> Github</a></span>
        </div>
      </section>

      <!-- /author  -->

      <section class="share">
        <h4>Share this post</h4>
        <a class="icon-twitter" href="http://twitter.com/share?text=[译] Android 架构：Part 2 —— 介绍 Clean Architecture&amp;url=https://github.com/listenzzandroid-architecture-part-2-clean-architecture.html"
          onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
          <span class="hidden">Twitter</span>
        </a>
        <a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https://github.com/listenzzandroid-architecture-part-2-clean-architecture.html"
          onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
          <span class="hidden">Facebook</span>
        </a>
        <a class="icon-google-plus" href="https://plus.google.com/share?url=https://github.com/listenzzandroid-architecture-part-2-clean-architecture.html"
          onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;">
          <span class="hidden">Google+</span>
        </a>
      </section>

      <!-- Add Disqus Comments -->
      <!-- <section class="disqus">
    <div id="disqus_thread"></div>
    <script type="text/javascript">
        var disqus_shortname  = 'listenzz';
        var disqus_identifier = '[译] Android 架构：Part 2 —— 介绍 Clean Architecture';
        var disqus_title      = "[译] Android 架构：Part 2 —— 介绍 Clean Architecture";
        
        (function() {
         var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
         dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
         })();
        </script>
    <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</section>
 -->

    </footer>

  </article>

</main>

<aside class="read-next">

  <!-- [[! next_post ]] -->
  
  <a class="read-next-story no-cover" href="/android-architecture-part-3-applying-clean-architecture-android.html">
    <section class="post">
      <h2>[译] Android 架构：Part 3 —— 应用 Clean Architecture</h2>
      <p>到目前为止，在这个系列中，我们已经讲解了一些 [初学者易犯的错误](./android-architecture-part-1-every-new-beginning-is-hard)，以及过了一遍 [Clean Architecture](./android-architecture-part-2-clean-architecture)。在这最后一部分，我们会介绍最后一个难题：标签，或者准确地说，组件。 > 译者注：看完了这一部分，还有第四部分。在第四部分将会提供一个很酷的示范项目。 首先，我会移除在 Android 项目中不使用的东西，添加一些在 Uncle Bob 的图中找不到的但我们需要使用的东西，看起来像这样： ![](../images/android/Graph-1-2.png) 我会从中心（抽象）讲到边缘（具体）。 ##...</p>
    </section>
  </a>
  
  <!-- [[! /next_post ]] -->
  <!-- [[! prev_post ]] -->
  
  <a class="read-next-story prev no-cover" href="/android-architecture-part-1-every-new-beginning-is-hard.html">
    <section class="post">
      <h2>[译] Android 架构：Part 1 —— 那些年我们犯过的错</h2>
      <p>本系列文章旨在概述我们搭建 Android 应用程序架构时可能会碰到的问题。我意识到，无论实现 Android app 架构的过程多么困难，结果证明这些一定是完成每一个卓越的应用的基础。 每种技术都有其自然的进化。或者更确切地说，它的社区经历了进化的过程。一个新的计算机语言或框架的早期采用者是爱好者，他们只是希望掌握技术，并尽快完成一些工作。通常，新社区规模小，在开发人员之间的知识传递潜力有限，也就是说，每个人都从自己的错误中学习，因为没有架构指南可用。 早期 Android 们的痛点：谷歌是否关心？ 你可以说，有很多资深的家伙在不同的技术上有很多的经验，但谁也没有时间提出标准。嗯，不一定。如果技术背后有一个强大的公司指望赚钱，他们的布道师会告诉你这个新语言多么酷，可以做很多事情，容易学习，可扩展，并且可以满足数以百万计的用户。 微软就经常用它的技术干这样的事情。另一方面，当谷歌收购了 Android， 我真的以为他们只是把它当作一个无关紧要的项目。 如果你从...</p>
    </section>
  </a>
  
  <!-- [[! /prev_post ]] -->
</aside>

<!-- /post -->


      <footer class="site-footer clearfix">
        <section class="copyright"><a href="/">在编码</a> &copy; 2020</section>
        <section class="poweredby">Proudly published with <a href="https://jekyllrb.com/">Jekyll</a> using <a href="https://github.com/biomadeira/jasper">Jasper</a></section>
      </footer>
    </div>
    <!-- [[! Ghost outputs important scripts and data with this tag ]] -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
    <!-- [[! The main JavaScript file for Casper ]] -->
    <script type="text/javascript" src="/assets/js/jquery.fitvids.js"></script>
    <script type="text/javascript" src="/assets/js/index.js"></script>
    <script type="text/javascript" src="/assets/js/prism.js"></script>

    <!-- Add Google Analytics  -->
    <!-- Google Analytics Tracking code -->
<script>
 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
   (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
   m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
 })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

 ga('create', 'UA-96120107-1', 'auto');
 ga('send', 'pageview');

</script>

  </body>
</html>
